package com.acme.javaschool.intense.sems.sem2concurrency.pool;

import com.acme.javaschool.intense.sems.sem2concurrency.pool.data.ObjectSource;
import com.acme.javaschool.intense.sems.sem2concurrency.pool.data.PoolableObject;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import static java.lang.Thread.sleep;

public abstract class BasePool<T> implements Pool<T> {

    final TestHelper testHelper = new TestHelper();

    protected final int SHRINKING_TIME_THRESHOLD_MILLIS = 10;
    protected final Lock lock;
    private final ExecutorService sweeperExecutor;

    protected int minBound;
    protected int maxBound;
    protected int shrinkSizeThreshold = 5;
    protected long lastThresholdSizeTimeMillis; // the last moment when availableItems became > threshold

    protected ObjectSource<T> source;

    public BasePool(ObjectSource<T> source, int minBound, int maxBound) {
        lastThresholdSizeTimeMillis = System.currentTimeMillis();
        this.source = source;
        this.minBound = minBound;
        this.maxBound = maxBound;
        shrinkSizeThreshold = (maxBound - minBound) / 2;
        lock = new ReentrantLock();
        
        sweeperExecutor = Executors.newSingleThreadExecutor();
        sweeperExecutor.submit(new Sweeper(SHRINKING_TIME_THRESHOLD_MILLIS));
    }

    @Override
    public PoolableObject<T> get() throws InterruptedException {
        return tryGet(-1);
    }

    @Override
    public PoolableObject<T> tryGet() throws InterruptedException {
        return tryGet(0);
    }

    protected void resizeIfNeeded() {
        try {
            lock.lock();
            if (getAvailableItemsCount() == 0) {
                if (getBound() < maxBound) {
                    expand();
                }
            } else {
                shrinkIfNeeded();
            }
        } finally {
            lock.unlock();
        }
    }

    private void shrinkIfNeeded() {
        if ( (getAvailableItemsCount() > shrinkSizeThreshold) &&
                (System.currentTimeMillis() - lastThresholdSizeTimeMillis > SHRINKING_TIME_THRESHOLD_MILLIS)) {
            shrink();
        }
    }

    protected abstract void shrink();

    protected abstract void expand();

    protected abstract int getTakenItemsCount();

    protected abstract void fillAvailableItems(int sizeDelta);

    protected class Sweeper implements Runnable {
        private int intervalMillis;

        public Sweeper(int intervalMillis) {
            this.intervalMillis = intervalMillis;
        }

        @Override
        public void run() {
            while (true) {
                try {
                    sleep(intervalMillis);
                } catch (InterruptedException e) {
                    break;
                }
                shrinkIfNeeded();
            }
        }
    }

    public TestHelper getTestHelper() {
        return testHelper;
    }

}
